/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package meteocal.entityManager;

import java.security.Principal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import meteocal.entity.Event;
import meteocal.entity.Group;
import meteocal.entity.Invitation;
import meteocal.entity.Notification;
import meteocal.entity.Status;
import meteocal.entity.Users;
import meteocal.exception.EmptyFieldException;
import meteocal.exception.InvalidDateException;
import meteocal.exception.PrivacyException;

/**
 *
 * @author andrea
 */
@Stateless
public class UserManager {
    
    @PersistenceContext
    private EntityManager em;
    
    @Inject
    Principal principal;
    
    /**
     * Saves the given user in the database.
     * @param user
     * @throws EntityExistsException if the user already exists in the database
     */
    public void save(Users user) throws EntityExistsException{
        user.setGroupName(Group.USERS);
        em.persist(user);
    }
    
    /**
     * Updates user informations.
     * @param user
     * @throws EmptyFieldException if at least one required field is empty.
     * @throws InvalidDateException if the given user's birthday is after today.
     * @throws IllegalArgumentException if the user doesn't exist.
     */
    public void update(Users user) throws EmptyFieldException, InvalidDateException{
        if(!exist(user))
            throw new IllegalArgumentException("The user doesn't exist");
        if(!checkFields(user))
            throw new EmptyFieldException();
        if(user.getBirthday() != null){
            if(user.getBirthday().after(Calendar.getInstance()))
                throw new InvalidDateException();
        }
        em.merge(user);
    }
    
    /**
     * Removes the user from the database.
     * @param user
     */
    public void delete(Users user){
        if(!exist(user))
            throw new IllegalArgumentException("The user doesn't exist");
        em.remove(em.merge(user));
    }
    
    /**
     * Adds a user to the Favorites list of the currently logged user.
     * @param user 
     */
    public void addFavorite(Users user){
        if(!exist(user))
            throw new IllegalArgumentException("The user doesn't exist");
        Users logged = getLoggedUser();
        List<Users> favorites = logged.getFavorites();
        if(!favorites.contains(user) && !logged.equals(user)){
            favorites.add(user);
            logged.setFavorites(favorites);
        }
        em.merge(logged);
    }
    
    /**
     * Adds many users to the Favorites list of the currently logged user.
     * @param users 
     */
    public void addFavorites(List<Users> users){
        for(Users user : users)
            addFavorite(user);
    }
    
    /**
     * Removes a user from the Favorites list of the currently logged user.
     * @param user 
     * @throws IllegalArgumentException if the given user isn't a logged user's
     * favorite.
     */
    public void removeFavorite(Users user){
        if(!exist(user))
            throw new IllegalArgumentException("The user doesn't exist");
        Users logged = getLoggedUser();
        List<Users> favorites = logged.getFavorites();
        if(!favorites.contains(user))
            throw new IllegalArgumentException("The user isn't in Favorites");
        favorites.remove(user);
        logged.setFavorites(favorites);
        em.merge(logged);
    }
    
    /**
     * Removes many users from the Favorites list of the currently logged user.
     * @param users
     * @throws IllegalArgumentException if at least one of the given users isn't
     * a logged user's favorite.
     */
    public void removeFavorites(List<Users> users){
        for(Users user : users)
            removeFavorite(user);
    }
    
    /**
     * Checks if the given user is a Favorite of the currently logged user.
     * @param user
     * @return true if he is, false otherwise
     * @throws IllegalArgumentException if the user doesn't exist.
     */
    public boolean isFavorite(Users user){
        if(!exist(user))
            throw new IllegalArgumentException("The user doesn't exist");
        List<Users> users = getLoggedUser().getFavorites();
        if(users.contains(user))
            return true;
        return false;
    }
    
    /**
     * Finds the logged user in the current session
     * @return the currently logged user
     */
    public Users getLoggedUser(){
        return em.find(Users.class, principal.getName());
    }
    
    /**
     * Retrieves all users stored in the database
     * @return the resulting list
     */
    public List<Users> getAllUsers(){
        Query query = em.createQuery("SELECT u FROM Users u");
        try{
            List<Users> users = query.getResultList();
            return users;
        }
        catch(NoResultException e){
            return null;
        }
    }
    
    /**
     * Retrieves all the events created by the logged user.
     * @return the resulting list
     */
    public List<Event> getCreatedEvents(){
        Query query = em.createQuery("SELECT e FROM Event e WHERE e.creator = :user");
        try{
            List<Event> events = query.setParameter("user", getLoggedUser())
                    .getResultList();
            return events;
        }
        catch(NoResultException e){
            return null;
        }
    }
    
    /**
     * Retrieves all the events to which the logged user participates.
     * @return the resulting list
     */
    public List<Event> getAcceptedEvents(){
        Query query = em.createQuery("SELECT e FROM Event e, Invitation i "
                + "WHERE e.id = i.event.id "
                + "AND i.user = :user "
                + "AND i.status = :accepted");
        try{
            List<Event> events = query.setParameter("user", getLoggedUser())
                    .setParameter("accepted", Status.ACCEPTED)
                    .getResultList();
            return events;
        }
        catch(NoResultException e){
            return null;
        }
    }
    
    /**
     * Retrieves all the public events created by the given user.
     * @param user
     * @return the resulting list
     * @throws PrivacyException if the user's calendar is private.
     */
    public List<Event> getCreatedEvents(Users user) throws PrivacyException{
        if(!exist(user))
            throw new IllegalArgumentException("The user doesn't exist");
        if(!user.getPrivacy())
            throw new PrivacyException();
        Query query = em.createQuery("SELECT e FROM Event e "
                + "WHERE e.creator = :user "
                + "AND e.visible = TRUE");
        try{
            List<Event> events = query.setParameter("user", user)
                    .getResultList();
            return events;
        }
        catch(NoResultException e){
            return null;
        }
    }
    
    /**
     * Retrieves only the public events to which the given user participates.
     * @param user
     * @return the resulting list (may be null)
     * @throws PrivacyException if the user's calendar is private.
     */
    public List<Event> getAcceptedEvents(Users user) throws PrivacyException{
        if(!exist(user))
            throw new IllegalArgumentException("The user doesn't exist");
        if(!user.getPrivacy())
            throw new PrivacyException();
        Query query = em.createQuery("SELECT e FROM Event e, Invitation i "
                + "WHERE e.id = i.event.id "
                + "AND i.user = :user "
                + "AND i.status = :accepted "
                + "AND e.visible = TRUE");
        try{
            List<Event> events = query.setParameter("user", user)
                    .setParameter("accepted", Status.ACCEPTED)
                    .getResultList();
            return events;
        }
        catch(NoResultException e){
            return null;
        }
    }
    
    /**
     * Checks if the logged user can see the given event.
     * @param event
     * @return true if the event is public or if the user is the creator or either
     * invited to it, false otherwise
     */
    public boolean canSeeEvent(Event event){
        if(event == null)
            return false;
        if(getCreatedEvents().contains(event))
            return true;
        if(event.isVisible())
            return true;
        for(Invitation i : getInvitations(getLoggedUser())){
            if(i.getEvent().equals(event))
                return true;
        }
        return false;
    }
    
    /**
     * Retrieves all the invitations received by the user.
     * @param user
     * @return the resulting list
     */
    public List<Invitation> getInvitations(Users user){
        if(!exist(user))
            throw new IllegalArgumentException("The user doesn't exist");
        Query query = em.createQuery("SELECT i FROM Invitation i WHERE i.user = :user");
        try{
            List<Invitation> invititations = query.setParameter("user", user)
                    .getResultList();
            return invititations;
        }
        catch(NoResultException e){
            return null;
        }
    }
    
    /**
     * Retrieves all the notifications sent to the user.
     * @param user
     * @return the resulting list
     */
    public List<Notification> getNotifications(Users user){
        if(!exist(user))
            throw new IllegalArgumentException("The user doesn't exist");
        Query query = em.createQuery("SELECT n FROM Notification n WHERE n.user = :user");
        try{
            List<Notification> notifications = query.setParameter("user", user)
                    .getResultList();
            return notifications;
        }
        catch(NoResultException e){
            return null;
        }
    }
    
    /**
     * Searches user by his email
     * @param email
     * @return the user if exists, null otherwise
     */
    public Users findByEmail(String email){
        if(email == null)
            return null;
        return em.find(Users.class, email);
    }
    
    /**
     * Searches users with the given keyword in their email
     * @param keyword
     * @param max number of results
     * @return the resulting list of users (may be null)
     */
    public List<Users> findByIncompleteEmail(String keyword, int max){
        Query query = em.createQuery("SELECT u FROM Users u WHERE u.email LIKE :email");
        try{
            List<Users> usersList = (List<Users>) query.setParameter("email", "%" + keyword + "%")
                    .setMaxResults(max)
                    .getResultList();
            return usersList;
        }
        catch(NoResultException e){
            return null;
        }
    }
    
    /**
     * Searches users having the given keyword in their name
     * @param keyword
     * @param max number of results
     * @return the resulting list of users (may be null)
     */
    public List<Users> findByName(String keyword, int max){
        Query query = em.createQuery("SELECT u FROM Users u WHERE u.name LIKE :name");
        try{
            List<Users> usersList = (List<Users>) query.setParameter("name", "%" + keyword + "%")
                    .setMaxResults(max)
                    .getResultList();
            return usersList;
        }
        catch (NoResultException e){
            return null;
        }
    }
    
    /**
     * Searches users having the given keyword in their surname
     * @param keyword
     * @param max number of results
     * @return the resulting list of users (may be null)
     */
    public List<Users> findBySurname(String keyword, int max){
        Query query = em.createQuery("SELECT u FROM Users u WHERE u.surname LIKE :surname");
        try{
            List<Users> usersList = (List<Users>) query.setParameter("surname", "%" + keyword + "%")
                    .setMaxResults(max)
                    .getResultList();
            return usersList;
        }
        catch (NoResultException e){
            return null;
        }
    }
    
    /**
     * Checks if a given user is registered
     * @param user
     * @return true if he is in the database, false otherwise
     */
    public boolean exist(Users user){
        return em.find(Users.class, user.getEmail()) != null;
    }

    /**
     * Builds a list of users by searching them through their email adresses.
     * @param emails
     * @return the resulting list
     */
    public List<Users> usersFromString(List<String> emails) {
        List<Users> users = new ArrayList<>();
        for (String s : emails) {
            Users u = findByEmail(s);
            if (u != null) {
                users.add(u);
            }
        }
        return users;
    }
    
    /**
     * Checks if there are any empty field that must be fulfilled.
     * @param user
     * @return true if there are no empty fields, false otherwise.
     */
    private boolean checkFields(Users user) {        
        String name = user.getName();
        String surname = user.getSurname();
        String pwd = user.getPassword();
        return name != null && surname != null && pwd != null;
    }
}
